# -*- coding:utf-8 -*-

"""
搜索结果缓存服务 - 处理多客户端搜索的同步问题
"""

import asyncio
from typing import List, Dict, Any, Optional
from datetime import datetime
from sqlalchemy import select, desc
from sqlalchemy.ext.asyncio import AsyncSession

from module_admin.entity.do.search_record_do import SearchRecord
from module_admin.service.blacklist_service import BlacklistService
from utils.log_util import logger


class SearchCacheService:
    """
    搜索结果缓存服务
    
    解决多客户端搜索的时间差和同步问题：
    1. 维护全局最新的 publish_time
    2. 每次搜索结果返回时更新缓存
    3. 对比新商品时使用缓存的最大 publish_time
    """
    
    # 内存缓存：{user_id: {keyword: max_publish_time}}
    # ✅ 支持多用户隔离：每个用户维护自己的搜索结果缓存
    # ⚠️ 强制使用新格式，不兼容旧格式（避免逻辑错误）
    _cache: Dict[int, Dict[str, int]] = {}
    _lock = asyncio.Lock()
    
    @classmethod
    async def get_max_publish_time(cls, db: AsyncSession, keyword: str, user_id: Optional[int] = None) -> int:
        """
        获取某关键词的最大发布时间
        
        优先从内存缓存读取，缓存未命中则从数据库读取
        ✅ 支持多用户隔离：每个用户维护自己的搜索结果缓存
        ⚠️ 强制要求user_id，不兼容旧格式（避免逻辑错误）
        
        Returns:
            最大发布时间戳，如果没有则返回0
        """
        if not user_id:
            logger.warning(f"⚠️ get_max_publish_time: user_id为None，无法正确隔离缓存: keyword={keyword}")
            return 0
        
        async with cls._lock:
            # ✅ 强制使用新格式：{user_id: {keyword: max_publish_time}}
            # 先从内存缓存读取
            if user_id in cls._cache and isinstance(cls._cache[user_id], dict):
                if keyword in cls._cache[user_id]:
                    return cls._cache[user_id][keyword]
            
            # 缓存未命中，从数据库读取
            try:
                # ✅ 按user_id过滤（支持多用户隔离）
                query = select(SearchRecord.publish_time).where(
                    SearchRecord.search_keyword == keyword,
                    SearchRecord.create_by == user_id,  # ✅ 强制要求user_id
                    SearchRecord.del_flag == '0',
                    SearchRecord.publish_time.isnot(None)
                ).order_by(desc(SearchRecord.publish_time)).limit(1)
                
                result = await db.execute(query)
                max_time = result.scalar_one_or_none()
                
                if max_time:
                    # ✅ 更新缓存（确保重启后能正确加载）
                    if user_id not in cls._cache:
                        cls._cache[user_id] = {}
                    cls._cache[user_id][keyword] = max_time
                    logger.info(f"✅ 从数据库加载max_publish_time: user_id={user_id}, keyword={keyword}, max_time={max_time}")
                    return max_time
                else:
                    # ✅ 数据库中没有记录，返回0（这是正常的，首次搜索）
                    logger.debug(f"数据库中没有关键词 '{keyword}' 的记录，max_publish_time=0（首次搜索）")
            except Exception as e:
                # 如果表不存在或其他数据库错误，记录警告并返回0（优雅降级）
                logger.error(f"❌ 无法从数据库读取最大发布时间: {e}，返回0", exc_info=True)
            
            return 0
    
    @classmethod
    async def update_max_publish_time(cls, keyword: str, publish_time: int, user_id: Optional[int] = None):
        """
        更新某关键词的最大发布时间（内存缓存）
        ✅ 支持多用户隔离：每个用户维护自己的搜索结果缓存
        ⚠️ 强制要求user_id，不兼容旧格式（避免逻辑错误）
        """
        if not user_id:
            logger.warning(f"⚠️ update_max_publish_time: user_id为None，无法更新缓存: keyword={keyword}")
            return
        
        async with cls._lock:
            # ✅ 强制使用新格式：{user_id: {keyword: max_publish_time}}
            if user_id not in cls._cache:
                cls._cache[user_id] = {}
            current_max = cls._cache[user_id].get(keyword, 0)
            if publish_time > current_max:
                cls._cache[user_id][keyword] = publish_time
                logger.debug(f"更新缓存: user_id={user_id}, {keyword} max_publish_time={publish_time}")
    
    @classmethod
    async def process_search_results(
        cls,
        db: AsyncSession,
        client_id: str,
        keyword: str,
        raw_results: List[Dict[str, Any]],
        user_id: Optional[int] = None  # ✅ 新增：用户ID（用于缓存隔离）
    ) -> Dict[str, Any]:
        """
        处理搜索结果（完整流程）
        
        流程：
        1. 黑名单过滤
        2. 排序（按 publish_time 降序）
        3. 获取当前最大 publish_time
        4. 筛选真正的新商品（publish_time > max）
        5. 标记并保存新商品
        6. 更新缓存
        7. 返回处理后的结果
        
        Args:
            db: 数据库会话
            client_id: 客户端ID
            keyword: 搜索关键词
            raw_results: 原始搜索结果
            
        Returns:
            {
                'keyword': 搜索关键词,
                'total': 总数,
                'filtered': 过滤后数量,
                'new_count': 新商品数量,
                'results': 处理后的结果列表,
                'new_items': 新商品列表（用于下单）
            }
        """
        if not raw_results:
            return {
                'keyword': keyword,
                'total': 0,
                'filtered': 0,
                'new_count': 0,
                'results': [],
                'new_items': []
            }
        
        total_count = len(raw_results)
        
        # 1. 黑名单过滤（按用户隔离）
        filtered_results = await BlacklistService.filter_search_results(db, raw_results, user_id=user_id)
        filtered_count = len(filtered_results)
        
        if filtered_count == 0:
            logger.info(f"[{client_id}] 黑名单过滤后无结果")
            return {
                'keyword': keyword,
                'total': total_count,
                'filtered': filtered_count,
                'new_count': 0,
                'results': [],
                'new_items': []
            }
        
        # 2. 排序（按 publish_time 降序，position 升序）
        sorted_results = sorted(
            filtered_results,
            key=lambda x: (
                -(x.get('publish_time', 0) or 0),  # publish_time 降序
                x.get('position', 999999)  # position 升序
            )
        )
        
        # 3. 获取当前最大 publish_time（按user_id隔离）
        max_publish_time = await cls.get_max_publish_time(db, keyword, user_id)
        
        # ✅ 移除首次运行检测：应该从数据库正确加载max_publish_time
        # ✅ 如果数据库中有记录，max_publish_time应该已经被正确加载
        # ✅ 如果数据库中没有记录，max_publish_time=0是正常的（首次搜索），应该允许自动下单
        
        # 4. 筛选真正的新商品（publish_time > max）
        truly_new_items = [
            item for item in sorted_results
            if (item.get('publish_time', 0) or 0) > max_publish_time
        ]
        
        new_count = len(truly_new_items)
        
        if new_count > 0:
            logger.info(f"[{client_id}] 发现 {new_count} 个新商品")
            
            # 5. 标记并保存新商品（如果表不存在，只更新内存缓存）
            try:
                for item in truly_new_items:
                    await cls._save_search_record(db, item, keyword, is_new=True, user_id=user_id)  # ✅ 传递user_id
                
                # 提交数据库
                await db.commit()
                
                # ✅ 6. 清理旧记录：如果该关键词的记录超过50条，删除最旧的（保留40条作为缓冲）
                if user_id:
                    await cls._cleanup_old_records(db, keyword, user_id, max_records=50, keep_records=40)
                    await db.commit()
            except Exception as e:
                # 如果表不存在或其他数据库错误，只记录警告，继续处理（优雅降级）
                logger.warning(f"保存搜索记录失败（可能表不存在）: {e}，仅使用内存缓存")
                await db.rollback()
            
            # 7. 更新缓存（取新商品中最大的 publish_time，按user_id隔离）
            max_new_publish_time = max(
                (item.get('publish_time', 0) or 0) for item in truly_new_items
            )
            await cls.update_max_publish_time(keyword, max_new_publish_time, user_id)
        
        # 8. 返回处理后的结果
        return {
            'keyword': keyword,
            'client_id': client_id,
            'total': total_count,
            'filtered': filtered_count,
            'new_count': new_count,
            'results': sorted_results,
            'new_items': truly_new_items,
            'max_publish_time': max_publish_time
        }
    
    @classmethod
    async def _save_search_record(
        cls,
        db: AsyncSession,
        item: Dict[str, Any],
        keyword: str,
        is_new: bool = True,
        user_id: Optional[int] = None  # ✅ 用户ID（用于多用户隔离）
    ):
        """
        保存搜索记录到数据库（支持多用户隔离）
        ✅ 每个用户维护自己的搜索结果，商品ID在同一用户同一关键词下唯一
        """
        item_id = item.get('item_id') or item.get('id')
        if not item_id:
            logger.warning(f"商品缺少ID: {item}")
            return
        
        if not user_id:
            logger.warning(f"保存搜索记录时user_id为None，无法保存: item_id={item_id}, keyword={keyword}")
            return
        
        # ✅ 检查是否已存在（按用户和关键词隔离）
        query = select(SearchRecord).where(
            SearchRecord.item_id == item_id,
            SearchRecord.search_keyword == keyword,
            SearchRecord.create_by == user_id,
            SearchRecord.del_flag == '0'
        )
        
        result = await db.execute(query)
        existing_record = result.scalar_one_or_none()
        
        if existing_record:
            # 已存在，更新最后发现时间
            existing_record.last_found_time = datetime.now()
            existing_record.is_new = False
        else:
            # 不存在，创建新记录
            # ✅ 使用TEXT类型后，可以保存最大65,535字节的文本，对于商品标题完全足够
            # TEXT类型会自动处理编码，无需手动截断（MySQL会自动处理）
            title = item.get('title') or ''
            # 仅作为极端保护：如果标题超过64KB（几乎不可能），记录警告
            if title:
                title_bytes = len(title.encode('utf-8'))
                if title_bytes > 65535:
                    # 按字节截断（而不是字符），确保不超过TEXT类型的限制
                    title_encoded = title.encode('utf-8')[:65535]
                    title = title_encoded.decode('utf-8', errors='ignore')
                    logger.warning(f"商品标题过长已截断: item_id={item_id}, 原始长度={title_bytes}字节")
            
            new_record = SearchRecord(
                item_id=item_id,
                title=title,
                price=item.get('price'),
                seller_id=item.get('seller_id'),
                seller_nick=item.get('seller_nick'),
                location=item.get('location'),
                publish_time=item.get('publish_time'),
                position=item.get('position'),
                image_url=item.get('image_url'),
                tag_name=item.get('tagname') or item.get('tagName') or item.get('tag_name') or '',
                search_keyword=keyword,
                is_new=is_new,
                del_flag='0',
                create_by=user_id,  # ✅ 设置用户ID（支持多用户隔离）
                dept_id=1  # ✅ 默认部门ID
            )
            db.add(new_record)
    
    @classmethod
    async def _cleanup_old_records(
        cls,
        db: AsyncSession,
        keyword: str,
        user_id: int,
        max_records: int = 50,
        keep_records: int = 40
    ):
        """
        清理旧记录：如果该关键词的记录超过max_records条，删除最旧的，保留keep_records条
        
        Args:
            db: 数据库会话
            keyword: 搜索关键词
            user_id: 用户ID
            max_records: 触发清理的阈值（例如：50条）
            keep_records: 保留的记录数（例如：40条）
        """
        try:
            # 查询该关键词的记录总数
            from sqlalchemy import func
            count_query = select(func.count()).select_from(SearchRecord).where(
                SearchRecord.search_keyword == keyword,
                SearchRecord.create_by == user_id,
                SearchRecord.del_flag == '0'
            )
            result = await db.execute(count_query)
            total_count = result.scalar_one() or 0
            
            if total_count > max_records:
                # 需要删除的记录数
                delete_count = total_count - keep_records
                
                # 查询最旧的记录ID（按last_found_time升序）
                from sqlalchemy import delete
                old_records_query = select(SearchRecord.id).where(
                    SearchRecord.search_keyword == keyword,
                    SearchRecord.create_by == user_id,
                    SearchRecord.del_flag == '0'
                ).order_by(SearchRecord.last_found_time.asc()).limit(delete_count)
                
                old_records = await db.execute(old_records_query)
                old_record_ids = [row[0] for row in old_records.fetchall()]
                
                if old_record_ids:
                    # 删除最旧的记录
                    delete_query = delete(SearchRecord).where(
                        SearchRecord.id.in_(old_record_ids)
                    )
                    await db.execute(delete_query)
                    logger.info(f"✅ 清理旧记录: user_id={user_id}, keyword={keyword}, 删除{len(old_record_ids)}条，保留{keep_records}条")
        except Exception as e:
            logger.warning(f"清理旧记录失败: {e}", exc_info=True)
            # 不抛出异常，避免影响主流程
    
    @classmethod
    async def clear_cache(cls, keyword: Optional[str] = None, user_id: Optional[int] = None):
        """
        清空缓存（按user_id隔离）
        ⚠️ 强制要求user_id，不兼容旧格式（避免逻辑错误）
        
        Args:
            keyword: 如果指定，只清空该关键词的缓存；否则清空全部
            user_id: 用户ID（必需，用于隔离缓存）
        """
        if not user_id:
            logger.warning(f"⚠️ clear_cache: user_id为None，无法清空缓存: keyword={keyword}")
            return
        
        async with cls._lock:
            # ✅ 强制使用新格式：{user_id: {keyword: max_publish_time}}
            if keyword:
                if user_id in cls._cache and isinstance(cls._cache[user_id], dict):
                    cls._cache[user_id].pop(keyword, None)
                    logger.info(f"清空缓存: user_id={user_id}, {keyword}")
            else:
                if user_id in cls._cache:
                    del cls._cache[user_id]
                    logger.info(f"清空用户缓存: user_id={user_id}")

